﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Rogan.Utility.Common
{
    public delegate object FastInvokeHandler(object target, object[] parameters);
    public delegate object FastCreateInstanceHandler();
    public delegate object FastPropertyGetHandler(object target);
    public delegate void FastPropertySetHandler(object target, object parameter);

    public static class DynamicCalls
    {
        private static readonly Dictionary<MethodInfo, FastInvokeHandler> DictInvoker = new Dictionary<MethodInfo, FastInvokeHandler>();
        private static readonly Dictionary<Type, FastCreateInstanceHandler> DictCreator = new Dictionary<Type, FastCreateInstanceHandler>();
        private static readonly Dictionary<PropertyInfo, FastPropertyGetHandler> DictGetter = new Dictionary<PropertyInfo, FastPropertyGetHandler>();
        private static readonly Dictionary<PropertyInfo, FastPropertySetHandler> DictSetter = new Dictionary<PropertyInfo, FastPropertySetHandler>();
        public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
        {
            Dictionary<MethodInfo, FastInvokeHandler> obj;
            Monitor.Enter(obj = DictInvoker);
            FastInvokeHandler result;
            try
            {
                if (DictInvoker.ContainsKey(methodInfo))
                {
                    result = DictInvoker[methodInfo];
                }
                else
                {
                    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new[]
					{
						typeof(object),
						typeof(object[])
					}, methodInfo.DeclaringType.Module);
                    ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
                    ParameterInfo[] parameters = methodInfo.GetParameters();
                    Type[] array = new Type[parameters.Length];
                    for (int i = 0; i < array.Length; i++)
                    {
                        if (parameters[i].ParameterType.IsByRef)
                        {
                            array[i] = parameters[i].ParameterType.GetElementType();
                        }
                        else
                        {
                            array[i] = parameters[i].ParameterType;
                        }
                    }
                    LocalBuilder[] array2 = new LocalBuilder[array.Length];
                    for (int j = 0; j < array.Length; j++)
                    {
                        array2[j] = iLGenerator.DeclareLocal(array[j], true);
                    }
                    for (int k = 0; k < array.Length; k++)
                    {
                        iLGenerator.Emit(OpCodes.Ldarg_1);
                        EmitFastInt(iLGenerator, k);
                        iLGenerator.Emit(OpCodes.Ldelem_Ref);
                        EmitCastToReference(iLGenerator, array[k]);
                        iLGenerator.Emit(OpCodes.Stloc, array2[k]);
                    }
                    if (!methodInfo.IsStatic)
                    {
                        iLGenerator.Emit(OpCodes.Ldarg_0);
                    }
                    for (int l = 0; l < array.Length; l++)
                    {
                        if (parameters[l].ParameterType.IsByRef)
                        {
                            iLGenerator.Emit(OpCodes.Ldloca_S, array2[l]);
                        }
                        else
                        {
                            iLGenerator.Emit(OpCodes.Ldloc, array2[l]);
                        }
                    }
                    if (!methodInfo.IsStatic)
                    {
                        iLGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);
                    }
                    else
                    {
                        iLGenerator.EmitCall(OpCodes.Call, methodInfo, null);
                    }
                    if (methodInfo.ReturnType == typeof(void))
                    {
                        iLGenerator.Emit(OpCodes.Ldnull);
                    }
                    else
                    {
                        EmitBoxIfNeeded(iLGenerator, methodInfo.ReturnType);
                    }
                    for (int m = 0; m < array.Length; m++)
                    {
                        if (parameters[m].ParameterType.IsByRef)
                        {
                            iLGenerator.Emit(OpCodes.Ldarg_1);
                            EmitFastInt(iLGenerator, m);
                            iLGenerator.Emit(OpCodes.Ldloc, array2[m]);
                            if (array2[m].LocalType.IsValueType)
                            {
                                iLGenerator.Emit(OpCodes.Box, array2[m].LocalType);
                            }
                            iLGenerator.Emit(OpCodes.Stelem_Ref);
                        }
                    }
                    iLGenerator.Emit(OpCodes.Ret);
                    FastInvokeHandler fastInvokeHandler = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
                    DictInvoker.Add(methodInfo, fastInvokeHandler);
                    result = fastInvokeHandler;
                }
            }
            finally
            {
                Monitor.Exit(obj);
            }
            return result;
        }
        public static FastCreateInstanceHandler GetInstanceCreator(Type type)
        {
            Dictionary<Type, FastCreateInstanceHandler> obj;
            Monitor.Enter(obj = DictCreator);
            FastCreateInstanceHandler result;
            try
            {
                if (DictCreator.ContainsKey(type))
                {
                    result = DictCreator[type];
                }
                else
                {
                    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, type, new Type[0], typeof(DynamicCalls).Module);
                    ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
                    iLGenerator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
                    iLGenerator.Emit(OpCodes.Ret);
                    FastCreateInstanceHandler fastCreateInstanceHandler = (FastCreateInstanceHandler)dynamicMethod.CreateDelegate(typeof(FastCreateInstanceHandler));
                    DictCreator.Add(type, fastCreateInstanceHandler);
                    result = fastCreateInstanceHandler;
                }
            }
            finally
            {
                Monitor.Exit(obj);
            }
            return result;
        }
        public static FastPropertyGetHandler GetPropertyGetter(PropertyInfo propInfo)
        {
            Dictionary<PropertyInfo, FastPropertyGetHandler> obj;
            Monitor.Enter(obj = DictGetter);
            FastPropertyGetHandler result;
            try
            {
                if (DictGetter.ContainsKey(propInfo))
                {
                    result = DictGetter[propInfo];
                }
                else
                {
                    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new[]
					{
						typeof(object)
					}, propInfo.DeclaringType.Module);
                    ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetGetMethod(), null);
                    EmitBoxIfNeeded(iLGenerator, propInfo.PropertyType);
                    iLGenerator.Emit(OpCodes.Ret);
                    FastPropertyGetHandler fastPropertyGetHandler = (FastPropertyGetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertyGetHandler));
                    DictGetter.Add(propInfo, fastPropertyGetHandler);
                    result = fastPropertyGetHandler;
                }
            }
            finally
            {
                Monitor.Exit(obj);
            }
            return result;
        }
        public static FastPropertySetHandler GetPropertySetter(PropertyInfo propInfo)
        {
            Dictionary<PropertyInfo, FastPropertySetHandler> obj;
            Monitor.Enter(obj = DictSetter);
            FastPropertySetHandler result;
            try
            {
                if (DictSetter.ContainsKey(propInfo))
                {
                    result = DictSetter[propInfo];
                }
                else
                {
                    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, null, new[]
					{
						typeof(object),
						typeof(object)
					}, propInfo.DeclaringType.Module);
                    ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.Emit(OpCodes.Ldarg_1);
                    EmitCastToReference(iLGenerator, propInfo.PropertyType);
                    iLGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetSetMethod(), null);
                    iLGenerator.Emit(OpCodes.Ret);
                    FastPropertySetHandler fastPropertySetHandler = (FastPropertySetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertySetHandler));
                    DictSetter.Add(propInfo, fastPropertySetHandler);
                    result = fastPropertySetHandler;
                }
            }
            finally
            {
                Monitor.Exit(obj);
            }
            return result;
        }
        private static void EmitCastToReference(ILGenerator ilGenerator, Type type)
        {
            if (type.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Unbox_Any, type);
                return;
            }
            ilGenerator.Emit(OpCodes.Castclass, type);
        }
        private static void EmitBoxIfNeeded(ILGenerator ilGenerator, Type type)
        {
            if (type.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, type);
            }
        }
        private static void EmitFastInt(ILGenerator ilGenerator, int value)
        {
            switch (value)
            {
                case -1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    ilGenerator.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    ilGenerator.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    ilGenerator.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    ilGenerator.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    ilGenerator.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    ilGenerator.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    ilGenerator.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    ilGenerator.Emit(OpCodes.Ldc_I4_8);
                    return;
                default:
                    if (value > -129 && value < 128)
                    {
                        ilGenerator.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                        return;
                    }
                    ilGenerator.Emit(OpCodes.Ldc_I4, value);
                    return;
            }
        }
    }
}
